/*
 * Copyright (c) 2021. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
 * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
 * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
 * Vestibulum commodo. Ut rhoncus gravida arcu.
 */

package com.xxpure.kmeans;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Random;

public class KMeans<E> {
    public final ArrayList<E> initialData;//初始数据
    private final ArrayList<E> samples; //样本
    private final ArrayList<E>[] C;

    /**
     * 构造函数
     *
     * @param initialData 原始数据
     * @param k           分类的种类数
     */
    public KMeans(ArrayList<E> initialData, int k) {
        C = new ArrayList[k];
        this.initialData = initialData;
        samples = getInitialSample(k);
        for (int i = 0; i < samples.size(); i++)
            C[i] = new ArrayList<>();
    }

    public ArrayList<E> getSamples() {
        return samples;
    }

    public ArrayList<E>[] getC() {
        return C;
    }

    private void clearC() {
        for (int i = 0; i < samples.size(); i++) {
            C[i].clear();
        }
    }

    /**
     * @param k 获得初始样本的数量
     * @return 返回样本数量
     */
    private ArrayList<E> getInitialSample(int k) {
        var sampleIndex = new HashSet<Integer>();
        var samples = new ArrayList<E>();
        var randomSample = new Random();
        int res = 0;
        while (samples.size() < k) {
            while (sampleIndex.contains(res))
                res = randomSample.nextInt(k);

            samples.add(initialData.get(res));
            sampleIndex.add(res);
        }
        return samples;
    }

    /**
     * @param compare 比较对象
     * @return 返回下一次分类之后的结果
     * @throws Exception 如果已经达到了最好的结果 则抛出异常
     */
    public ArrayList<E> nextSample(KMeansInterface<E> compare) throws Exception {
        clearC();
        for (var element : initialData) {
            int minIndex = 0;
            int minDistance = Integer.MAX_VALUE - 1;
            for (int i = 0; i < samples.size(); i++) {
                int elementDistance = compare.getDistance(element, samples.get(i));
                if (elementDistance < minDistance) {
                    minDistance = elementDistance;
                    minIndex = i;
                }
            }
            C[minIndex].add(element);
        }
        boolean flag = true;
        for (int i = 0; i < samples.size(); i++) {
            var newSample = compare.getAverageSample(C[i]);

            if (!samples.get(i).equals(newSample)) {
                samples.set(i, newSample);
                flag = false;
            }
        }
        if (flag || compare.isOver())
            throw new Exception("this samples is the best");
        return samples;
    }
}
